﻿//功用：数据保存
//创建时间：2020-12-24 14:10:11
//作者：王浩力
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using WebNetCorePhoneErp.Model;
using WebNetCorePhoneErp.IDAL;
using WebNetCorePhoneErp.Model.Tool;

namespace WebNetCorePhoneErp.DAL
{
    /// <summary>
    /// 数据处理 实现
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class DaoBase<T> : IDaoBase<T> where T : class //BaseEntity//,new()
    {
        /*
         * https://docs.microsoft.com/zh-cn/ef/core/dbcontext-configuration/#avoiding-dbcontext-threading-issues
         避免 DbContext 线程处理问题
Entity Framework Core 不支持在同一 DbContext 实例上运行多个并行操作。 这包括异步查询的并行执行以及从多个线程进行的任何显式并发使用。 因此，始终立即 await 异步调用，或对并行执行的操作使用单独的 DbContext 实例。
当 EF Core 检测到尝试同时使用 DbContext 实例的情况时，你将看到 InvalidOperationException，其中包含类似于以下内容的消息：
在上一个操作完成之前，第二个操作已在此上下文中启动。 这通常是由使用同一个 DbContext 实例的不同线程引起的，但不保证实例成员是线程安全的。
检测不到并发访问时，可能会导致未定义的行为、应用程序崩溃和数据损坏         
         */
        //每次查询使用新的DbContext，防止并发异常

        /// <summary>
        /// ef数据上下文
        /// </summary>
        protected readonly MyDbContext dbContext;

        /// <summary>
        /// 当前T类型对应的表
        /// </summary>
        protected readonly DbSet<T> dbSet;

        public DaoBase()
        {
            dbContext = new MyDbContext();
            dbSet = dbContext.Set<T>();
        }

        /// <summary>
        /// 获取新的DbContext
        /// </summary>
        /// <returns></returns>
        public MyDbContext GetNewDbContext()
        {
            return new MyDbContext();
        }

        /// <summary>
        /// 获取没有上下文缓存的新查询
        /// </summary>
        /// <returns></returns>
        protected IQueryable<T> GetDbsetNoCacheQueryable()
        {
            return dbSet.AsNoTracking();
        }

        public int Add(T entity)
        {
            dbSet.Add(entity);
            return dbContext.SaveChanges();
        }

        public async Task<int> AddAsync(T entity)
        {
            dbSet.Add(entity);
            return await dbContext.SaveChangesAsync();
        }

        public int Add(IEnumerable<T> entitys)
        {
            foreach (var entity in entitys)
            {
                dbSet.Add(entity);
            }
            return dbContext.SaveChanges();
        }

        public async Task<int> AddListAsync(IEnumerable<T> entitys)
        {
            //  await dbSet.AddRangeAsync(entitys);
            dbSet.AddRange(entitys);
            return await dbContext.SaveChangesAsync();
        }

        public int Update(T entity)
        {
            MyDbContext dbContextUpdate = new MyDbContext();
            dbContextUpdate.Entry(entity).State = EntityState.Modified;
            return dbContextUpdate.SaveChanges();
        }

        public async Task<int> UpdateAsync(T entity)
        {
            try
            {
                DbContext dbContextUpdate = new MyDbContext();
                dbContextUpdate.Entry(entity).State = EntityState.Modified;
                return await dbContextUpdate.SaveChangesAsync();
            }
            catch (Exception e)
            {
                return 0;
            }
        }

        public int Update(IEnumerable<T> entitys)
        {
            DbContext dbContextUpdate = new MyDbContext();
            foreach (var entity in entitys)
            {
                dbContextUpdate.Entry(entity).State = EntityState.Modified;
            }
            return dbContextUpdate.SaveChanges();
        }

        public async Task<int> UpdateAsync(T entity, string[] column)
        {
            DbContext dbContextUpdate = new MyDbContext();
            var entry = dbContextUpdate.Entry(entity);
            entry.State = EntityState.Modified;
            foreach (var item in column)
            {
                entry.Property(item).IsModified = true;
            }
            var listPv = entry.Properties.ToList();
            listPv.RemoveAll(w => w.Metadata.IsPrimaryKey());
            foreach (var item in listPv)
            {
                //字段名称 
                string itemName = item.Metadata.Name;
                if (column.Contains(itemName))
                {
                    continue;
                }
                entry.Property(itemName).IsModified = false;
            }
            return await dbContextUpdate.SaveChangesAsync();
        }

        /// <summary>
        /// [事务]异步修改集合，修改指定的列
        /// </summary>
        /// <param name="list">集合</param>
        /// <param name="column">指定的列，参考 id,name。要修改的列名传入数组</param>
        /// <returns></returns>
        public async Task<int> UpdateListAsync(List<T> list, string[] column)
        {
            DbContext dbContextUpdate = new MyDbContext();
            var tran = dbContextUpdate.Database.BeginTransaction();
            int count = 0;
            try
            {
                foreach (var entity in list)
                {
                    var entry = dbContextUpdate.Entry(entity);
                    entry.State = EntityState.Modified;
                    foreach (var item in column)
                    {
                        entry.Property(item).IsModified = true;
                    }
                    var listPv = entry.Properties.ToList();
                    listPv.RemoveAll(w => w.Metadata.IsPrimaryKey());
                    foreach (var item in listPv)
                    {
                        //字段名称
                        string itemName = item.Metadata.Name;
                        //bool isKey= item.Metadata.IsPrimaryKey();
                        //if (isKey)
                        //{
                        //    continue;
                        //}
                        if (column.Contains(itemName))
                        {
                            continue;
                        }
                        //item.IsModified = false;
                        entry.Property(itemName).IsModified = false;
                    }
                }
                count = await dbContextUpdate.SaveChangesAsync();
                tran.Commit();
            }
            catch (Exception)
            {
                tran.Rollback();
                throw;
            }
            finally
            {
                tran.Dispose();
            }
            return count;
        }

        /// <summary>
        ///   [事务]批量修改或新增或删除(若干个)
        /// </summary>
        /// <param name="list">要操作的记录</param>
        /// <returns></returns>
        public async Task<int> SaveMultipleAsync(List<SaveModel> list)
        {
            int count = 0;
            if (list == null || list.Count == 0)
            {
                return 0;
            }
            DbContext dbContextUpdate = new MyDbContext();
            IDbContextTransaction tran = null;
            try
            {
                tran = dbContextUpdate.Database.BeginTransaction();
                foreach (var item in list)
                {
                    var entityEntry = dbContextUpdate.Entry(item.Model);

                    //删除记录
                    if (item.IsDelete)
                    {
                        entityEntry.State = EntityState.Deleted;
                        continue;
                    }

                    //System.Reflection.PropertyInfo fieldInfo = item.ModelType.GetProperty("IsUpdate");
                    //bool isUpdate = Convert.ToBoolean(fieldInfo.GetValue(item.Model));
                    // if (isUpdate)
                    if (item.Columns != null && item.Columns.Length > 0)
                    {
                        //if (item.Columns == null || item.Columns.Count() == 0)
                        //{
                        //    throw new ArgumentNullException("修改时，必须传要修改的列，参数Columns必须");
                        //}

                        //修改记录
                        //var entityEntry = dbContextUpdate.Entry(item.Model);                    
                        entityEntry.State = EntityState.Modified;
                        foreach (var cols in item.Columns)
                        {
                            entityEntry.Property(cols).IsModified = true;
                        }
                        var listP2 = entityEntry.Properties.ToList();
                        listP2.RemoveAll(w => w.Metadata.IsPrimaryKey());
                        foreach (var propertyN in listP2)
                        {
                            //字段名称
                            string itemName = propertyN.Metadata.Name;
                            if (item.Columns.Contains(itemName))
                            {
                                continue;
                            }
                            entityEntry.Property(itemName).IsModified = false;
                        }
                    }
                    else
                    {
                        //新增记录
                        dbContextUpdate.Add(item.Model);
                        //dbContextUpdate.Set(item.ModelType).Add(item.Model);
                        //dbContextUpdate.Set<item.ModelType>.Add(item.Model);
                    }
                }
                count = await dbContextUpdate.SaveChangesAsync();
                if (count > 0)
                {
                    tran.Commit();
                }
            }
            catch (Exception ex)
            {
                var ex2 = ex.InnerException ?? ex;
                ex2 = ex2.InnerException ?? ex2;
                LogHelpter.AddLog("SaveMultipleAsync(List<SaveModel> list)执行异常：" + ex2.ToString());
                count = 0;
                tran.Rollback();
                throw ex2;
            }
            finally
            {
                tran.Dispose();
            }
            return count;
        }

        public int Delete(T entity)
        {
            dbSet.Attach(entity);
            dbSet.Remove(entity);
            return dbContext.SaveChanges();
        }

        public async Task<int> DeleteByPrimaryKeyAsync(object id)
        {
            MyDbContext myDbContext = new MyDbContext();
            var entityEntry = myDbContext.Set<T>();
            Microsoft.EntityFrameworkCore.Metadata.IKey key = entityEntry.EntityType.FindPrimaryKey();
            var list = key.Properties;
            var first = list.First();
            //表名
            string tableName = key.DeclaringEntityType.ClrType.Name;
            //字段类型
            string typeName = first.ClrType.Name;
            //字段名称
            string keyName = first.Name;
            string sql = string.Empty;
            switch (typeName)
            {
                case "String":
                case "Guid":
                    sql = $"delete from [{tableName}]  where [{keyName}]='{id}';";
                    break;
                case "Int64":
                case "Int32":
                    sql = $"delete from [{tableName}] where [{keyName}]={id};";
                    break;
                default:
                    throw new DbUpdateException("没有分析出主键，无法删除数据");
                    //break;
            }
            int count = await myDbContext.Database.ExecuteSqlRawAsync(sql);
            return count;
        }

        public async Task<int> DeleteAsync(T entity)
        {
            dbSet.Attach(entity);
            dbSet.Remove(entity);
            return await dbContext.SaveChangesAsync();
        }

        public int Delete(Expression<Func<T, bool>> where)
        {
            var entitys = this.GetList(where);
            foreach (var entity in entitys)
            {
                dbSet.Remove(entity);
            }
            return dbContext.SaveChanges();
        }

        public async Task<int> DeleteAsync(Expression<Func<T, bool>> where)
        {
            var entitys = await dbSet.Where(where).ToListAsync();
            dbSet.RemoveRange(entitys);
            return await dbContext.SaveChangesAsync();
        }

        public T Get(Expression<Func<T, bool>> where)
        {
            MyDbContext dbContext = GetNewDbContext();
            return dbContext.Set<T>().AsNoTracking().Where(where).FirstOrDefault();
        }

        /// <summary>
        ///根据主键异步查询
        /// </summary>
        /// <param name="id">主键</param>
        /// <returns></returns>
        /// <!-- 2019-9-27 16:31:31 王浩力 创建  -->
        public T Get(object id)
        {
            MyDbContext dbContext = GetNewDbContext();
            var model = dbContext.Set<T>().Find(id);
            //var model = dbSet.Find(id);
            if (model != null)
            {
                //设置不跟踪上下文实体，有利于修改数据保存
                var entity = dbContext.Entry(model);
                entity.State = EntityState.Detached;
            }
            return model;
        }

        /// <summary>
        ///根据主键异步查询
        /// </summary>
        /// <param name="id">主键</param>
        /// <returns></returns>
        /// <!-- 2019-7-16 22:52:35 王浩力 创建  -->
        public async Task<T> GetAsync(object id)
        {
            MyDbContext dbContext = GetNewDbContext();
            var model = await dbContext.Set<T>().FindAsync(id);
            if (model != null)
            {
                //设置不跟踪上下文实体，有利于修改数据保存
                var entity = dbContext.Entry(model);
                if (entity.State == EntityState.Unchanged)
                {
                    entity.State = EntityState.Detached;
                }
            }
            return model;
        }

        public async Task<T> GetSingleOrDefaultAsync(Expression<Func<T, bool>> where)
        {
            MyDbContext dbContext = GetNewDbContext();
            return await dbContext.Set<T>().AsNoTracking().SingleOrDefaultAsync(where);
        }

        public List<T> GetListAll()
        {
            return GetDbsetNoCacheQueryable().ToList();
        }

        public List<T> GetListAll(Expression<Func<T, bool>> where)
        {
            return GetDbsetNoCacheQueryable().Where(where).ToList();
        }

        public IQueryable<T> GetList(Expression<Func<T, bool>> where)
        {
            if (where == null) return GetDbsetNoCacheQueryable();
            return GetDbsetNoCacheQueryable().Where(where);
        }

        public IQueryable<T> GetListByNewContext(Expression<Func<T, bool>> where)
        {
            DbContext dataContext = new MyDbContext();
            if (where == null) return dataContext.Set<T>().AsNoTracking();
            return dataContext.Set<T>().AsNoTracking().Where(where);
        }

        public IQueryable<T> GetQuery()
        {
            return GetDbsetNoCacheQueryable();
        }

        public IQueryable<T> GetQuery(Expression<Func<T, bool>> where)
        {
            if (where == null) return GetDbsetNoCacheQueryable();
            return GetDbsetNoCacheQueryable().Where(where);
        }

        public async Task<List<T>> GetListAsync(Expression<Func<T, bool>> where)
        {
            MyDbContext dbContext = GetNewDbContext();
            var list = await dbContext.Set<T>().Where(where).ToListAsync();
            //var list = await GetDbsetNoCacheQueryable().Where(where).ToListAsync();
            return list;
        }

        public async Task<List<T>> GetAllListAsync()
        {
            return await GetDbsetNoCacheQueryable().ToListAsync();
        }

        public bool Any(Expression<Func<T, bool>> @where)
        {
            return GetDbsetNoCacheQueryable().Any(where);
        }

        public async Task<bool> AnyAsync(Expression<Func<T, bool>> where)
        {
            return await GetDbsetNoCacheQueryable().AnyAsync(where);
        }

        public int Count(Expression<Func<T, bool>> @where)
        {
            return GetDbsetNoCacheQueryable().Count(where);
        }

        public async Task<int> CountAsync(Expression<Func<T, bool>> where)
        {
            int count = await GetDbsetNoCacheQueryable().Where(where).CountAsync();
            return count;
        }

        /// <summary>
        /// 分页排序查询
        /// </summary>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <param name="where"></param>
        /// <param name="orderwhere"></param>
        /// <returns></returns>
        public PageModel<T> GetPage<SortKey>(int pageIndex, int pageSize, Expression<Func<T, bool>> where, Expression<Func<T, SortKey>> orderwhere)
        {
            int count = GetDbsetNoCacheQueryable().Where(where).Count();
            var list = GetDbsetNoCacheQueryable().Where(where).OrderBy(orderwhere).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
            return new PageModel<T>(list, count, pageIndex, pageSize);
        }

        /// <summary>
        /// 表联合查询时，可用此方法
        /// </summary>
        /// <typeparam name="G">视图实体类型，装了多个表字段的实体</typeparam>
        /// <param name="query"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public PageModel<G> GetPage<G>(IQueryable<G> query, int pageIndex, int pageSize)
        {
            int count = query.Count();
            var list = query.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
            return new PageModel<G>(list, count, pageIndex, pageSize);
        }

        /// <summary>
        /// 表联合查询时，可用此方法
        /// </summary>
        /// <typeparam name="G">视图实体类型，装了多个表字段的实体</typeparam>
        /// <param name="query"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public async Task<PageModel<G>> GetPageAsync<G>(IQueryable<G> query, int pageIndex, int pageSize)
        {
            int count = await query.CountAsync();
            var list = await query.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
            return new PageModel<G>(list, count, pageIndex, pageSize);
        }

        /// <summary>
        /// 当前实体类型的分页
        /// </summary>
        /// <typeparam name="SortKey"></typeparam>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <param name="where"></param>
        /// <param name="orderByAsc"></param>
        /// <returns></returns>
        public async Task<PageModel<T>> GetPageAsync<SortKey>(int pageIndex, int pageSize, Expression<Func<T, bool>> where, Expression<Func<T, SortKey>> orderByAsc)
        {
            int count = await GetDbsetNoCacheQueryable().Where(where).CountAsync();
            var list = await GetDbsetNoCacheQueryable().Where(where).OrderBy(orderByAsc).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
            return new PageModel<T>(list, count, pageIndex, pageSize);
        }

        public async Task<PageModel<T>> GetPageOrderByDescAsync<SortKey>(int pageIndex, int pageSize, Expression<Func<T, bool>> where, Expression<Func<T, SortKey>> orderByDesc)
        {
            int count = await GetDbsetNoCacheQueryable().Where(where).CountAsync();
            var list = await GetDbsetNoCacheQueryable().Where(where).OrderByDescending(orderByDesc).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
            return new PageModel<T>(list, count, pageIndex, pageSize);
        }

        /// <summary>
        /// 扩展 IQueryable<T>.ToListAsync();
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        public async Task<List<G>> ToListAsync<G>(IQueryable<G> query)
        {
            var list = await query.ToListAsync();
            return list;
        }


    }
}
